﻿unit APIs;

interface
uses
  SysUtils, Types, Classes, Variants,
  DateUtils, MMSystem,Windows, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, IdHTTP,
  FireDAC.Stan.Option, FireDAC.Stan.Error, FireDAC.UI.Intf, FireDAC.Phys.Intf,
  FireDAC.Stan.Def, FireDAC.Stan.Pool, FireDAC.Stan.Async, FireDAC.Phys,
  Data.DB, FireDAC.Comp.Client, FireDAC.Phys.MySQL, FireDAC.Phys.MySQLDef,
  FireDAC.Stan.Param, FireDAC.DatS, FireDAC.DApt.Intf, FireDAC.DApt,
  FireDAC.Comp.DataSet, FireDAC.FMXUI.Wait, FireDAC.Comp.UI, Vcl.Forms,FMX.Media,
  XMLDoc,xmldom,XMLIntf,IdHashMessageDigest,IdGlobal,IdHash;


var PathOfTrain:String;

type TChoices=record
  Up,Down,Left,Right:String;
end;

type TChoiceQuiz = record
  Target: String;
  PlayedFile: String;
  Choices: TChoices;
end;

type TPlayList = class
  private
    FDir: String;
    FState: Integer;
    FTimer: TTimer;
    FPlayer: TMediaPlayer;
    FCount: Integer;
    procedure TimerEvent(ASender: TObject);
    function CountFiles(ADir: String): Integer;
  public
    constructor Create;
    destructor Destroy;override;
    procedure Play(ADir: String);
end;

{
create wavlists from one directory
}
type TWavLists = class
  private
    FLists: TStringList;
    FPlayer: TMediaPlayer;
    function CreateFileList(ADir: String): TStringList;
  public
    constructor Create(ADir: String);
    destructor Destroy;override;
    function Play(AStr: String): String;

end;

type TXmlWrap = class
  private
  public
    FXml: IXMLDocument;
    FSel: IDomNodeSelect;
    constructor Create;
    destructor Destroy;override;
    procedure Load(AXML: String);
    function LoadFile(AFileName: String): Boolean;
    function Get(APath: String): String;
    function Count(APath: String): Integer;
    function Geti(APath: String;AIndex: Integer): String;

    procedure ConvertVarToDefaultValue(ANode: IXMLNode);
    procedure Replace(ADest,ASource: IXMLNode);

    //clone child nodes in AValue and copy it(them) to ANode
    procedure Assign(ADest,ASource: IXMLNode);
    procedure AssignByPath(ANode: IXMLNode;ADestPath,ASourcePath: String);
    procedure AssignTextByPath(ANode: IXMLNode;APath,AText: String);
    procedure AssignValueByPath(ANode: IXMLNode;APath:String;AValue: IXMLNode);

    function SelectNode(ANode: IXMLNode; APath: String): IXMLNode;
    function RootNode: IXMLNode;

    function IsVar(ANode: IXMLNode): Boolean;
    procedure ConvertAllValueToDefault(ANode: IXMLNode);

    function PathOfNode(ARNode,ANode: IXMLNode): String;
    function PathOfVar(ANode,AVar: IXMLNode): String;
    function NameOfVar(AVar: IXMLNode): String;





end;

type TOnRecv = procedure(ASender: TObject) of object;

type TMsgBox = record
  Address: String;
  Content: TXmlWrap;
end;

type
  PMethod = ^TMethod;
  TMultiEvent = procedure(const AType,AValue : string) of object;
  TMultiEventClass = class
  private
    fMultiEventHandlers : TList;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Add(AEvent: TMultiEvent);
    procedure Remove(AEvent: TMultiEvent);
    procedure Fire(AType,AMsg: String);
end;


type TServerApi = class
  private
    FIdHttp: TIdHttp;
    function Post(AUrl: String;AParams,AValues:array of String): String;
  public
    constructor Create;
    destructor Destroy;override;
    function UserRegist(APhone,APassword,APasswordConfirm,ACaptcha: String): String;
    function UserLogin(APhone,APassword: String): String;
    function UserGetCaptcha(APhone: String): String;
    function UserForgetPassword(APhone,APassword,APasswordConfirm,ACaptcha: String): String;
    function UserGetInfomation(AUid: String): String;
    function UserGetProgress(AUid: String): String;
    function ServerTime: String;
    function GetTable: String;
    function StartTraining(AUid: String): String;
    function UpdateTrain(AUid,ATableId,ATrainId,AProgress: String): String;
    function TrainVersion(ATrainId:String): String;
    function MainVersion:String;
    function TrainLog(AUid,ATrainId,AXmlVal: String): String;
end;

type TReflectMessage = record
  Time: DWord;
  Msg: String;
end;

type TWaitFor = class
  private
   FWaitingMessage: String;
   FCheckPoint:DWord;
  public
    Reflect1: TReflectMessage;
    constructor Create;
    destructor Destroy;override;
    procedure WaitFor(Time: Integer);
    procedure SetCheckPoint;
    procedure WaitTo(ATime: Integer);
    function WaitForReflect(AMsg: String;ATimeOut: Integer): Integer;
    function SameMessage(AMsg1,AMsg2: String): Boolean;
    function GetCheckPoint: DWord;
end;

type TRecordOfSoundsOfPhonetics = record
  Symbol,Speaker,TypeOf,Store: String;
end;

type TRandomTraverse = class
  private
    Count1: Integer;
    Group1: array of Integer;
    Group2: array of Integer;
  public
    constructor Create(ACount: Integer);
    destructor Destroy;override;
    procedure Reset;
    function Index: Integer;
end;

type TQueryList = class
  private
    Connection1: TFDConnection;
  public
    Querys: TStringList;
    constructor Create(AConnection: TFDConnection);
    destructor Destroy;override;
    procedure AddQuery(AName,ASQL: String);
    function FindQuery(AName: String): TFDQuery;
    procedure DeleteQuery(AName: String);
    procedure DeleteQueryByIndex(AIndex: Integer);
    function GetQueryByIndex(AIndex: Integer): TFDQuery;
end;

//create,add,play
type TSoundList = class
  private
    ResourceConnection1: TFDConnection;
    QueryList1: TQueryList;
    MediaPlayer1: TMediaPlayer;
  public
    constructor Create;
    destructor Destroy;override;
    procedure Add(ASymbol,ASQL: String);
    function Play(ASymbol: String):String;
    function Count: Integer;
    function Symbol(AIndex: Integer): String;
    procedure Replay;
end;

{
  give a group of targets and their weights
  generator a chain of targets
}


{
  input: <ChoiceCount>,<Targets>
}

type TChoiceQuizGenerator = class
  private
    FXml: TXmlWrap;
    FTargets: TStringList;
    FChoiceCount: Integer;
    FTraverse: TRandomTraverse;
    FOnEvent: TMultiEvent;
    procedure Send(const AType,AValue: String);
  public
    Quiz: TChoiceQuiz;
    constructor Create(AChoiceCount: Integer;ATargets:TStringList);
    destructor Destroy;override;
    procedure Next;
    function AnswerIsRight(AAnswer: String): Boolean;
    procedure Recv(const AType,AValue: String);
  end;

type TWeightTraverse = class
  private
    FGroup: array of Integer;
  public
    Weights: array of Integer;
    constructor Create(ACount: Integer);
    destructor Destroy;override;
    function Index: Integer;
end;

type TQuizCreator=class
  private
  public
    constructor Create;
    destructor Destroy;override;
end;

//record sounds to files ,play file by symbol
type TSoundsRecorder = class
  private
    FCount: Integer;
    FDir: String;
    FPlayer: TMediaPlayer;
    FMic: TAudioCaptureDevice;
    FSounds: TStringList;
  public
    constructor Create(ADir: String);
    destructor Destroy;override;
    procedure Clear;
    procedure Rec(ASymbol: String);
    procedure Stop;
    function Play(ASymbol: String): Boolean;
end;

type TSoundRecorder = class
  private
    FPlayer: TMediaPlayer;
    FMic: TAudioCaptureDevice;
  public
    constructor Create;
    destructor Destroy;override;
    procedure Rec(AFileName: String);
    procedure Play(AFileName: String);
    procedure Stop;
end;
type TLogOfTrain = record
    User,Config: String;
    Date:TDate;
    Time:TTime;
    Target:String;
    Answer:String;
    Match: Boolean;
    ReflectTime: Integer;
end;
type TLogsOfTrain = class
  private
    FUser,FConfig: String;
    FConnection: TFDConnection;
    FQuery: TFDQuery;
  public
    constructor Create(AUser,AConfig: String);
    destructor Destroy;override;
    procedure WriteLog(ALogOfTrain:TLogOfTrain);

end;

//save log to table;load log;
type TLogsOfTain2a = class
  private
    FUser,FConfig: String;
    FConnection: TFDConnection;
    FQuery: TFDQuery;
  public
    constructor Create(AUser,AConfig:String);
    destructor Destroy;override;
    procedure WriteLog(ADate:TDate;ATime:TTime;ATarget:String;AMatch:Boolean;AReflectTime: Integer);
end;

type TConfigOfTrain1 = class
  private
    XML1: IXMLDocument;
    procedure GetSpeakers(var ASpeakers: array of String);
    procedure GetTargets(var ATargets: array of String);
  public
    constructor Create(AXML: String); //one section of configs.xml
    destructor Destroy;override;
    function Target(AIndex: Integer): String;
    function TargetCount: Integer;
    function Speaker(AIndex: Integer): String;
    function SpeakerCount: Integer;
    function Name: String;
    function Interval: Integer;
    procedure Brief(AStrings: TStringList);
    function Param(AName: String): String;
end;
type TConfigOfTrain2 = class
  private
    XML1: IXMLDocument;
    procedure GetSpeakers(var ASpeakers: array of String);
    procedure GetTargets(var ATargets: array of String);
  public
    constructor Create(AXML: String); //one section of configs.xml
    destructor Destroy;override;
    function Target(AIndex: Integer): String;
    function TargetCount: Integer;
    function Speaker(AIndex: Integer): String;
    function SpeakerCount: Integer;
    function Name: String;
    function Interval: Integer;
    function ChoiceCount: Integer;
    function Noise: Integer;
    procedure Brief(AStrings: TStringList);
    function Param(AName: String): String;
end;

type TConfigsOfTrain1 = class
  private
    FConfigs: TStringList;
  public
    constructor Create(AFileName: String);
    destructor Destroy;override;
    function ConfigCount: Integer;
    function Config(AIndex: Integer): TConfigOfTrain1;
    function ConfigByName(AName: String): TConfigOfTrain1;
end;
type TConfigsOfTrain2 = class
  private
    FConfigs: TStringList;
  public
    constructor Create(AFileName: String);
    destructor Destroy;override;
    function ConfigCount: Integer;
    function Config(AIndex: Integer): TConfigOfTrain2;
    function ConfigByName(AName: String): TConfigOfTrain2;
end;
{
type TMessageCenter = class
  private
  public
    constructor Create;
    destructor Destroy;override;
    function Regist(
end;
}

const VowelsKK : array[0..16]  of String =
       ('aɪ','aʊ','ɔɪ','e','i','o','u','æ','ɛ','ɪ','ɔ','ʊ','ɑ','ʌ','ə','ɝ','ɚ');
const ConsonantsKK : array[0..23] of String =
       ('p','t','k','f','s','ɵ','ʃ','tʃ','h','b','d','g','v','z','ð','ʒ','dʒ','m','n','ŋ','l','r','j','w');

const CreateSoundsOfPhoneticsSQL =
  'CREATE TABLE SoundsOfPhonetics {Symbol char(10),Speaker char(10),TyperOf char(10),Store char(100)}';

const URLOfSoundsOfPhonetics ='http://sounds-of-phonetics.oss-cn-beijing.aliyuncs.com';


//call initialize at the beginning and call uninitialize when quit
procedure Initialize;
procedure Uninitialize;
//get md5 code of string
function GetMD5Hash(Str:String):String;
//visit query via table name
//Query(Table).Open(......)
var QueryList: TStringList;
function InsertQuery(AName: String;AQuery: TFDQuery): Integer;
function FindQuery(AName: String): TFDQuery;
function RemoveQuery(AName: String): TFDQuery;   //remove item do not destroy the object
procedure CreateQueryList;
procedure CreateQuery(AName: String;AConnection: TFDConnection;ASQL: String);

var CommonPath: String;

var ServerApi: TServerApi;

var ReflectMessage: TReflectMessage;  //record reflect message


var RemoteConnection: TFDConnection;
var ResourceConnection: TFDConnection;
var LogsConnection: TFDConnection;
var ResourceIdHttp: TIdHttp;

function ExePath: String;
procedure DownloadFile(AURL,AFileName: String);
procedure DownloadFiles(AURLs: TStringList);
procedure URLToLocalFileName(AURL: String;var AFileName: String);
procedure CreateDownloadFileList(var AFileList: TStringList);

procedure DatabaseUpload(ADatabase,ATable: String);

procedure OpenLocalDataSource;
procedure CloseLocalDataSource;



function CreateRemoteConnection(ADatabase: String): TFDConnection;
function CreateLocalConnection(ADatabaseFile: string): TFDConnection;
function CreateIdHttp: TIdHttp;

//copy ATable from ASource and copy them to ADest (First clear ADest)
procedure CopyTable(ADest,ASource: TFDQuery;ATable:String);
//copy ATable from ASource and append them to ADest
procedure AppendTable(ADest,ASource: TFDQuery;ATable:String);

procedure FillTableOfSyllables(AError: TStringList);
procedure AppendRecordsOfSyllables(AQuery:TFDQuery;AHeads,ATails:array of String;
            AType,ASpeaker:String;AError: TStringList);


implementation

constructor TQueryList.Create(AConnection: TFDConnection);
begin
  Querys:=TStringList.Create;
  Connection1:=AConnection;
end;
destructor TQueryList.Destroy;
var
  i: Integer;
  Count1:Integer;
begin
  Count1:=Querys.Count;
  for i := 0 to Count1-1 do begin
    DeleteQueryByIndex(0);
  end;
  Querys.Free;
  inherited;
end;

procedure TQueryList.AddQuery(AName,ASQL: String);
var Query1: TFDQuery;
begin
  Query1:=TFDQuery.Create(Application.MainForm);
  Query1.Connection:=Connection1;
  Query1.Open(ASQL);
  Querys.AddObject(AName,Query1);
end;
function TQueryList.FindQuery(AName: String): TFDQuery;
var i: Integer;
begin
  i:=Querys.IndexOf(AName);
  if i>=0 then begin
    Result:=TFDQuery(Querys.Objects[i]);
  end;
end;

function TQueryList.GetQueryByIndex(AIndex: Integer): TFDQuery;
begin
  Result:=TFDQuery(Querys.Objects[AIndex]);
end;

procedure TQueryList.DeleteQuery(AName: String);
var i: Integer;
    Query1: TFDQuery;
begin
  i:=Querys.IndexOf(AName);
  Query1:=TFDQuery(Querys.Objects[i]);
  Query1.Close;
  Querys.Delete(i);
  Query1.Free;
end;

procedure TQueryList.DeleteQueryByIndex(AIndex: Integer);
var Query1: TFDQuery;
begin
  Query1:=TFDQuery(Querys.Objects[AIndex]);
  Query1.Close;
  Querys.Delete(AIndex);
  Query1.Free;
end;

function ExePath: String;
begin
  Result:=ExtractFilePath(Application.ExeName);
end;

procedure Initialize;
var Query1: TFDQuery;
begin
  ResourceConnection:=CreateLocalConnection(ExePath+'resource.db');
  LogsConnection:=CreateLocalConnection(ExePath+'logs.db');
  CreateQueryList;
end;
procedure Uninitialize;
begin
end;

//the remote connection and the query has been created and connected
//the local connection and the query has been created and connected
procedure DatabaseUpload(ADatabase,ATable: String);
var LocalDatabase: String;
begin
  LocalDatabase:=ExePath+ADatabase+'.db';
end;


procedure CopyTable(ADest,ASource: TFDQuery;ATable:String);
begin
  ASource.Open('select * from '+ATable);
  ADest.Open('select * from '+ATable);
  ADest.CopyDataSet(ASource,[]);
end;
procedure AppendTable(ADest,ASource: TFDQuery;ATable:String);
begin
  ASource.Open('select * from '+ATable);
  ADest.Open('select * from '+ATable);
  ADest.CopyDataSet(ASource,[coAppend]);

end;

procedure OpenRemoteDataSource;
begin
  RemoteConnection.Connected:=True;
end;
procedure CloseRemoteDataSource;
begin
  RemoteConnection.Connected:=False;
end;

//Connection1 := CreateLocalConnection('resource.db')
function CreateLocalConnection(ADatabaseFile: string): TFDConnection;
begin
  Result:=TFDConnection.Create(Application.MainForm);
  with Result.Params do begin
    Add('Database='+ADatabaseFile);
    Add('LockingMode=Normal');
    Add('OpenMode=ReadWrite');
    Add('StringFormat=Unicode');
    Add('DriverID=SQLite');
  end;
end;

//Connection1:=CreateRemoteConnection('resource')
function CreateRemoteConnection(ADatabase: String): TFDConnection;
begin
  Result:=TFDConnection.Create(Application.MainForm);
  with Result.Params do begin
    Add('Database='+ADatabase);
    Add('User_Name=tertio');
    Add('Password=z3l4w5');
    Add('Server=tongtianta.mysql.rds.aliyuncs.com');
    Add('CharacterSet=utf8');
    Add('DriverID=MySQL');
  end;
end;

procedure CreateQuery(AName: String;AConnection: TFDConnection;ASQL: String);
var Query1: TFDQuery;
begin
  Query1:=TFDQuery.Create(Application.MainForm);
  Query1.Connection:=AConnection;
  Query1.Open(ASQL);
  InsertQuery(AName,Query1);
end;


procedure OpenLocalDataSource;
begin
  ResourceConnection.Connected:=True;
end;
procedure CloseLocalDataSource;
begin
  ResourceConnection.Connected:=False;
end;

function CreateIdHttp: TIdHttp;
begin
  Result:=TIdHttp.Create(Application.MainForm);
end;

procedure DownloadFile(AURL,AFileName: String);
var File1: TFileStream;
begin
  if not DirectoryExists(ExtractFilePath(AFileName)) then
    ForceDirectories(ExtractFilePath(AFileName));
  File1:=TFileStream.Create(AFileName,fmCreate);
  ResourceIdHttp.Get(ResourceIdHttp.URL.URLEncode(AURL),File1);
end;

//automatically create local path (protocal)
procedure DownloadFiles(AURLs: TStringList);
var i:Integer;
    FileName1: String;
begin
  for i := 0 to AURLs.Count-1 do begin
    URLToLocalFileName(AURLs.Strings[i],FileName1);
    DownloadFile(AURLs.Strings[i],FileName1);
  end;
end;

procedure CreateDownloadFileList(var AFileList: TStringList);
var Dir1: String;
    Name1: String;
    i: Integer;
begin
  Dir1:='http://sounds-of-phonetics.oss-cn-beijing.aliyuncs.com/';

  if assigned(AFileList) then begin
    AFileList.Free;
  end;
  AFileList:=TStringList.Create;


  //alina/vowels/
  for i := 0 to 16 do begin
    AFileList.Add(Dir1+'alina/vowels/'+vowelsKK[i]+'.wav');
    AFileList.Add(Dir1+'alina/vowels/'+vowelsKK[i]+#39+'.wav');
  end;
  //alina/consonants/
  for i := 0 to 23 do begin
    AFileList.Add(Dir1+'alina/consonants/'+ConsonantsKK[i]+'.wav');
    AFileList.Add(Dir1+'alina/consonants/'+ConsonantsKK[i]+#39+'.wav');
  end;
  //julia/vowels/
  for i := 0 to 16 do begin
    AFileList.Add(Dir1+'julia/vowels/'+vowelsKK[i]+'.wav');
    AFileList.Add(Dir1+'julia/vowels/'+vowelsKK[i]+#39+'.wav');
  end;
  //julia/consonants/
  for i := 0 to 23 do begin
    AFileList.Add(Dir1+'julia/consonants/'+ConsonantsKK[i]+'.wav');
    AFileList.Add(Dir1+'julia/consonants/'+ConsonantsKK[i]+#39+'.wav');
  end;

end;
procedure AppendRecordsOfSyllables(AQuery:TFDQuery;AHeads,ATails:array of String;
             AType,ASpeaker: String;AError: TStringList);
var i,j:Integer;
  Head1,Tail1,Store1: String;
  FileName1: String;
begin
  for i := 0 to Length(AHeads)-1 do begin
    for j := 0 to Length(ATails)-1 do begin
      Head1:=AHeads[i];
      Tail1:=ATails[j];
      Store1:=ASpeaker+'\'+Head1+Tail1+'.wav';
      FileName1:=ExePath+'resource\soundsofsyllables\'+Store1;
      if not FileExists(FileName1) then begin
        AError.Add(Store1);
      end else begin
        AQuery.AppendRecord([Head1,Tail1,ASpeaker,AType,Store1]);
      end;
    end;
  end;
end;
procedure FillTableOfSyllables(AError: TStringList);
var i,j:Integer;
  Connection1:TFDConnection;
  Query1: TFDQuery;
  Head1,Tail1,Speaker1,TypeOf1,Store1: String;
  FileName1: String;
begin
  Connection1:=CreateLocalConnection(ExePath+'resource.db');
  Query1:=TFDQuery.Create(Application.MainForm);
  Query1.Connection:=Connection1;

  Query1.Open('select * from SoundsOfSyllables');

  Speaker1:='alina';
  AppendRecordsOfSyllables(Query1,VowelsKK,ConsonantsKK,'vc',Speaker1,AError);
  AppendRecordsOfSyllables(Query1,ConsonantsKK,VowelsKK,'cv',Speaker1,AError);
  AppendRecordsOfSyllables(Query1,VowelsKK,VowelsKK,'vv',Speaker1,AError);
  AppendRecordsOfSyllables(Query1,ConsonantsKK,ConsonantsKK,'cc',Speaker1,AError);

  Speaker1:='brian';
  AppendRecordsOfSyllables(Query1,VowelsKK,ConsonantsKK,'vc',Speaker1,AError);
  AppendRecordsOfSyllables(Query1,ConsonantsKK,VowelsKK,'cv',Speaker1,AError);
  AppendRecordsOfSyllables(Query1,VowelsKK,VowelsKK,'vv',Speaker1,AError);
  AppendRecordsOfSyllables(Query1,ConsonantsKK,ConsonantsKK,'cc',Speaker1,AError);

  Speaker1:='joe';
  AppendRecordsOfSyllables(Query1,VowelsKK,ConsonantsKK,'vc',Speaker1,AError);
  AppendRecordsOfSyllables(Query1,ConsonantsKK,VowelsKK,'cv',Speaker1,AError);
  AppendRecordsOfSyllables(Query1,VowelsKK,VowelsKK,'vv',Speaker1,AError);
  AppendRecordsOfSyllables(Query1,ConsonantsKK,ConsonantsKK,'cc',Speaker1,AError);

  Speaker1:='julia';
  AppendRecordsOfSyllables(Query1,VowelsKK,ConsonantsKK,'vc',Speaker1,AError);
  AppendRecordsOfSyllables(Query1,ConsonantsKK,VowelsKK,'cv',Speaker1,AError);
  AppendRecordsOfSyllables(Query1,VowelsKK,VowelsKK,'vv',Speaker1,AError);
  AppendRecordsOfSyllables(Query1,ConsonantsKK,ConsonantsKK,'cc',Speaker1,AError);

  Speaker1:='shimiaoning';
  AppendRecordsOfSyllables(Query1,VowelsKK,ConsonantsKK,'vc',Speaker1,AError);
  AppendRecordsOfSyllables(Query1,ConsonantsKK,VowelsKK,'cv',Speaker1,AError);
  AppendRecordsOfSyllables(Query1,VowelsKK,VowelsKK,'vv',Speaker1,AError);
  AppendRecordsOfSyllables(Query1,ConsonantsKK,ConsonantsKK,'cc',Speaker1,AError);

  Speaker1:='stephanie';
  AppendRecordsOfSyllables(Query1,VowelsKK,ConsonantsKK,'vc',Speaker1,AError);
  AppendRecordsOfSyllables(Query1,ConsonantsKK,VowelsKK,'cv',Speaker1,AError);
  AppendRecordsOfSyllables(Query1,VowelsKK,VowelsKK,'vv',Speaker1,AError);
  AppendRecordsOfSyllables(Query1,ConsonantsKK,ConsonantsKK,'cc',Speaker1,AError);

  Speaker1:='vincent';
  AppendRecordsOfSyllables(Query1,VowelsKK,ConsonantsKK,'vc',Speaker1,AError);
  AppendRecordsOfSyllables(Query1,ConsonantsKK,VowelsKK,'cv',Speaker1,AError);
  AppendRecordsOfSyllables(Query1,VowelsKK,VowelsKK,'vv',Speaker1,AError);
  AppendRecordsOfSyllables(Query1,ConsonantsKK,ConsonantsKK,'cc',Speaker1,AError);

  Query1.Close;
  Connection1.Close;
end;


//URL: http://sounds-of-phonetics.oss-cn-beijing.aliyuncs.com/alina/consonants/b.wav
//Filename:  ExeDir+ '\' + alina/consonants/b.wav
procedure URLToLocalFileName(AURL: String; var AFileName: String);
var tm1: TMatch;
    i: Integer;
    FileName1: String;
begin
  tm1:=TRegEx.Match(AURL,'http://.+.com/(.+)');
  FileName1:=tm1.Groups.Item[1].Value;
  AFileName:=ExePath+'\'+TRegEx.Replace(FileName1,'/','\');
end;


procedure TWaitFor.SetCheckPoint;
begin
  FCheckPoint:=GetTickCount;
end;

procedure TWaitFor.WaitFor(Time: Integer);
var T1,T2: DWord;
begin
  T1:=GetTickCount;
  while True do begin
    Application.ProcessMessages;
    T2:=GetTickCount;
    if (T2-T1)>Time then break;
  end;
end;

//wait from checkpoint to ...
procedure TWaitFor.WaitTo(ATime: Integer);
var T1: TDateTime;
begin
  while True do begin
    Application.ProcessMessages;
    T1:=GetTickCount;
    if (T1-FCheckPoint)>ATime then break;
  end;

end;
{ATimeOut = 0 :without time limit
 Result:
 Result = -1 :time out
}
function TWaitFor.WaitForReflect(AMsg: String; ATimeOut: Integer): Integer;
var T1,T2: DWord;
begin
  T1:=GetTickCount;
  if ATimeOut = 0 then begin
    while not SameMessage(Reflect1.Msg,AMsg) do begin
      Application.ProcessMessages;
    end;
  end else begin
    while not SameMessage(Reflect1.Msg,AMsg) do begin
      Application.ProcessMessages;
      T2:=GetTickCount;
      if (T2-T1)>ATimeOut then begin
        Result:=-1;
        Exit;
      end;
    end;
  end;
  T2:=GetTickCount;
  Result:= T2-T1;
end;


constructor TWaitFor.Create;
begin

end;

destructor TWaitFor.Destroy;
begin

  inherited;
end;

function TWaitFor.GetCheckPoint: DWord;
begin
  Result:=FCheckPoint;
end;

function TWaitFor.SameMessage(AMsg1,AMsg2: String): Boolean;
begin
  Result:= (LowerCase(AMsg1) = LowerCase(AMsg2));
end;

function InsertQuery(AName: String;AQuery: TFDQuery): Integer;
begin
  Result:= QueryList.AddObject(AName,AQuery);

end;
function FindQuery(AName: String): TFDQuery;
var i: Integer;
begin
  i:=QueryList.IndexOf(AName);
  Result:=TFDQuery(QueryList.Objects[i]);
end;
function RemoveQuery(AName: String): TFDQuery;
var i: Integer;
begin
  i:=QueryList.IndexOf(AName);
  Result:=TFDQuery(QueryList.Objects[i]);
  QueryList.Delete(i);
end;
procedure CreateQueryList;
begin
  QueryList:=TStringList.Create;
end;

{ TPhoneticList }

procedure TSoundList.Add(ASymbol, ASQL: String);
begin
  QueryList1.AddQuery(ASymbol,ASQL);
end;

function TSoundList.Count: Integer;
begin
  Result:=QueryList1.Querys.Count;
end;

constructor TSoundList.Create;
var ExePath1:String;
begin
  ExePath1:=ExtractFilePath(Application.ExeName);
  ResourceConnection1:=CreateLocalConnection(ExePath1+'resource.db');
  QueryList1:=TQueryList.Create(ResourceConnection1);
  MediaPlayer1:=TMediaPlayer.Create(Application.MainForm);
end;

destructor TSoundList.Destroy;
begin
  MediaPlayer1.Free;
  QueryList1.Free;
  ResourceConnection1.Close;
  ResourceConnection1.Free;
  inherited;
end;

function TSoundList.Play(ASymbol: String): String;
var Query1:TFDQuery;
    FileName1: String;
    i: Integer;
begin
  Query1:=QueryList1.FindQuery(ASymbol);
  Query1.RecNo:= Random(Query1.RecordCount);
  FileName1:=ExePath+'resource\'+Query1.Table.Name+'\'+Query1.FieldByName('store').AsString;
  //ShowMessage('play: '+ FileName1);
  if FileExists(FileName1) then begin
    MediaPlayer1.FileName:=FileName1;
    MediaPlayer1.Play;
    Result:=FileName1;
  end else begin
    ShowMessage('File not exists:'+FileName1);
  end;
end;

procedure TSoundList.Replay;
begin
  MediaPlayer1.Stop;
  MediaPlayer1.Play;
end;

function TSoundList.Symbol(AIndex: Integer): String;
begin
  Result:=QueryList1.Querys.Strings[AIndex];
end;

{ TRandomTraverse }

constructor TRandomTraverse.Create(ACount: Integer);
var i:Integer;
begin
  Count1:=ACount;
  Reset;
end;

destructor TRandomTraverse.Destroy;
begin
  inherited;
end;

function TRandomTraverse.Index: Integer;
var i:Integer;
begin
  i:=Random(Length(Group1));
  Result:=Group1[i];
  Delete(Group1,i,1);
  if Length(Group1)=0 then begin
    Reset;
  end;
end;

procedure TRandomTraverse.Reset;
var i:Integer;
begin
  SetLength(Group1,Count1);
  SetLength(Group2,0);
  for i := 0 to Count1-1 do begin
    Group1[i]:=i;
  end;
end;

{ TConfigOfTrain1 }
procedure TConfigOfTrain1.Brief(AStrings: TStringList);
begin
  AStrings.Assign(XML1.XML);
end;

function TConfigOfTrain1.Param(AName: String): String;
begin
  Result:=XML1.ChildNodes.First.ChildNodes.FindNode(AName).Text;
end;

constructor TConfigOfTrain1.Create(AXML: String);
begin
  XML1:=TXMLDocument.Create(nil);
  XML1.LoadFromXML(AXML);
  XML1.Active:=True;
end;

destructor TConfigOfTrain1.Destroy;
begin
  inherited;
end;

procedure TConfigOfTrain1.GetSpeakers(var ASpeakers: array of String);
begin

end;

procedure TConfigOfTrain1.GetTargets(var ATargets: array of String);
begin

end;

function TConfigOfTrain1.Interval: Integer;
begin
  Result:=StrToInt(Param('interval'));
end;

function TConfigOfTrain1.Name: String;
begin
  Result:=XML1.ChildNodes.First.ChildNodes.FindNode('name').Text;
end;

function TConfigOfTrain1.Speaker(AIndex: Integer): String;
var Root1: IXMLNode;
begin
  Root1:=XML1.ChildNodes.FindNode('config').ChildNodes.FindNode('speakers');
  Result:=Root1.ChildNodes.Get(AIndex).NodeValue;
end;

function TConfigOfTrain1.SpeakerCount: Integer;
var Root1: IXMLNode;
begin
  Root1:=XML1.ChildNodes.FindNode('config').ChildNodes.FindNode('speakers');
  Result:=Root1.ChildNodes.Count;
end;

function TConfigOfTrain1.Target(AIndex: Integer): String;
var Root1: IXMLNode;
begin
  Root1:=XML1.ChildNodes.FindNode('config').ChildNodes.FindNode('targets');
  Result:=Root1.ChildNodes.Get(AIndex).NodeValue;
end;

function TConfigOfTrain1.TargetCount: Integer;
var Root1: IXMLNode;
begin
  Root1:=XML1.ChildNodes.FindNode('config').ChildNodes.FindNode('targets');
  Result:=Root1.ChildNodes.Count;
end;

{ TConfigOfTrain1 }
procedure TConfigOfTrain2.Brief(AStrings: TStringList);
begin
  AStrings.Assign(XML1.XML);
end;

function TConfigOfTrain2.Param(AName: String): String;
begin
  Result:=XML1.ChildNodes.First.ChildNodes.FindNode(AName).Text;
end;

function TConfigOfTrain2.ChoiceCount: Integer;
begin
  Result:=StrToInt(Param('choice_count'));
end;

constructor TConfigOfTrain2.Create(AXML: String);
begin
  XML1:=TXMLDocument.Create(nil);
  XML1.LoadFromXML(AXML);
  XML1.Active:=True;
end;

destructor TConfigOfTrain2.Destroy;
begin
  inherited;
end;

procedure TConfigOfTrain2.GetSpeakers(var ASpeakers: array of String);
begin

end;

procedure TConfigOfTrain2.GetTargets(var ATargets: array of String);
begin

end;

function TConfigOfTrain2.Interval: Integer;
begin
  Result:=StrToInt(Param('interval'));
end;

function TConfigOfTrain2.Name: String;
begin
  Result:=XML1.ChildNodes.First.ChildNodes.FindNode('name').Text;
end;

function TConfigOfTrain2.Noise: Integer;
begin
  Result:=StrToInt(Param('noise'));
end;

function TConfigOfTrain2.Speaker(AIndex: Integer): String;
var Root1: IXMLNode;
begin
  Root1:=XML1.ChildNodes.FindNode('config').ChildNodes.FindNode('speakers');
  Result:=Root1.ChildNodes.Get(AIndex).NodeValue;
end;

function TConfigOfTrain2.SpeakerCount: Integer;
var Root1: IXMLNode;
begin
  Root1:=XML1.ChildNodes.FindNode('config').ChildNodes.FindNode('speakers');
  Result:=Root1.ChildNodes.Count;
end;

function TConfigOfTrain2.Target(AIndex: Integer): String;
var Root1: IXMLNode;
begin
  Root1:=XML1.ChildNodes.FindNode('config').ChildNodes.FindNode('targets');
  Result:=Root1.ChildNodes.Get(AIndex).NodeValue;
end;

function TConfigOfTrain2.TargetCount: Integer;
var Root1: IXMLNode;
begin
  Root1:=XML1.ChildNodes.FindNode('config').ChildNodes.FindNode('targets');
  Result:=Root1.ChildNodes.Count;
end;

{ TConfigsOfTrain1 }
function TConfigsOfTrain1.Config(AIndex: Integer): TConfigOfTrain1;
begin
  Result:=TConfigOfTrain1(FConfigs.Objects[AIndex]);
end;

function TConfigsOfTrain1.ConfigByName(AName: String): TConfigOfTrain1;
var Index1: Integer;
begin
  Index1:=FConfigs.IndexOf(AName);
  Result:=TConfigOfTrain1(FConfigs.Objects[Index1]);
end;

function TConfigsOfTrain1.ConfigCount: Integer;
begin
  Result:=FConfigs.Count;
end;

constructor TConfigsOfTrain1.Create(AFileName: String);
var i:Integer;
    Count1: Integer;
    Config1: TConfigOfTrain1;
    XML1: IXMLDocument;
    Text1: String;
begin
  FConfigs:=TStringList.Create;
  XML1:=TXMLDocument.Create(AFileName);
  XML1.Active:=True;
  Count1:=XML1.ChildNodes.First.ChildNodes.Count;
  for i := 0 to Count1-1 do begin
    Text1:=XML1.ChildNodes.First.ChildNodes.Get(i).XML;
    Config1:=TConfigOfTrain1.Create(Text1);
    FConfigs.AddObject(Config1.Name,Config1);
  end;
end;

destructor TConfigsOfTrain1.Destroy;
var i:Integer;
    Config1: TConfigOfTrain1;
begin
  for i := 0 to ConfigCount-1 do begin
     Config1:=Config(i);
     Config1.Free;
  end;
  FConfigs.Free;
  inherited;
end;

{ TConfigsOfTrain2 }
function TConfigsOfTrain2.Config(AIndex: Integer): TConfigOfTrain2;
begin
  Result:=TConfigOfTrain2(FConfigs.Objects[AIndex]);
end;

function TConfigsOfTrain2.ConfigByName(AName: String): TConfigOfTrain2;
var Index1: Integer;
begin
  Index1:=FConfigs.IndexOf(AName);
  Result:=TConfigOfTrain2(FConfigs.Objects[Index1]);
end;

function TConfigsOfTrain2.ConfigCount: Integer;
begin
  Result:=FConfigs.Count;
end;

constructor TConfigsOfTrain2.Create(AFileName: String);
var i:Integer;
    Count1: Integer;
    Config1: TConfigOfTrain2;
    XML1: IXMLDocument;
    Text1: String;
begin
  FConfigs:=TStringList.Create;
  XML1:=TXMLDocument.Create(AFileName);
  XML1.Active:=True;
  Count1:=XML1.ChildNodes.First.ChildNodes.Count;
  for i := 0 to Count1-1 do begin
    Text1:=XML1.ChildNodes.First.ChildNodes.Get(i).XML;
    Config1:=TConfigOfTrain2.Create(Text1);
    FConfigs.AddObject(Config1.Name,Config1);
  end;
end;

destructor TConfigsOfTrain2.Destroy;
var i:Integer;
    Config1: TConfigOfTrain2;
begin
  for i := 0 to ConfigCount-1 do begin
     Config1:=Config(i);
     Config1.Free;
  end;
  FConfigs.Free;
  inherited;
end;

{ TSoundsRecorder }

procedure TSoundsRecorder.Clear;
var i,j:integer;
    Files1: TStringList;
    FileName1: String;
    Count1: Integer;
begin
  for i := 0 to FSounds.Count-1 do begin
     Files1 := TStringList(FSounds.Objects[i]);
     for j := 0 to Files1.Count-1 do begin
       FileName1:=Files1.Strings[j];
       DeleteFile(PWideChar(FileName1));
     end;
  end;
  Count1:=FSounds.Count;
  for i := 0 to Count1-1 do begin
    Files1:=TStringList(FSounds.Objects[0]);
    Files1.Free;
    FSounds.Delete(0);
  end;
  FSounds.Free;
end;

{
recorder
save logs
quiz
  show
  answer
  showcorrect(1)
savelog().
}
constructor TSoundsRecorder.Create(ADir: String);
begin
  FPlayer:=TMediaPlayer.Create(nil);
  FSounds:=TStringList.Create;
  FMic := TCaptureDeviceManager.Current.DefaultAudioCaptureDevice;
end;

destructor TSoundsRecorder.Destroy;
begin
  FPlayer.Stop;
  FPlayer.Free;
  FSounds.Free;
  inherited;
end;

function TSoundsRecorder.Play(ASymbol: String): Boolean;
var i:Integer;
    Files1: TStringList;
    Index1: Integer;
    FileName1: String;
begin
  Result:=True;
  Index1:=FSounds.IndexOf(ASymbol);
  if Index1<0 then begin
    Result:=False;
    Exit;
  end;

  Files1:=TStringList(FSounds.Objects[Index1]);
  if Files1.Count=0 then begin
    Result:=False;
    Exit;
  end;

  Index1:=Random(Files1.Count);
  FileName1:=Files1.Strings[Index1];

  FPlayer.Stop;
  FPlayer.FileName:=FileName1;
  FPlayer.Play;
end;

procedure TSoundsRecorder.Rec(ASymbol: String);
var i,Index1: Integer;
    Files1: TStringList;
    FileName1: String;
begin
  if FSounds.IndexOf(ASymbol)<0 then begin
    Files1:=TStringList.Create;
    FSounds.AddObject(ASymbol,Files1);
  end;

  FileName1:=FDir+IntToStr(FCount)+'.wav';
  Index1:=FSounds.IndexOf(ASymbol);
  Files1:=TStringList(FSounds.Objects[Index1]);
  Files1.Add(FileName1);

  FMic.FileName:=FileName1;
  FMic.StartCapture;

  FCount:=FCount+1;
end;

procedure TSoundsRecorder.Stop;
begin
  if FMic.State = TCaptureDeviceState.Capturing then begin
    FMic.StopCapture;
  end;
  if FPlayer.State = TMediaState.Playing then begin
    FPlayer.Stop;
  end;
end;

{ TSoundRecorder }

constructor TSoundRecorder.Create;
begin
  FPlayer:=TMediaPlayer.Create(nil);
  FMic := TCaptureDeviceManager.Current.DefaultAudioCaptureDevice;
end;

destructor TSoundRecorder.Destroy;
begin
  FPlayer.Free;
  inherited;
end;

procedure TSoundRecorder.Play(AFileName: String);
begin
  FPlayer.FileName:=AFileName;
  FPlayer.Play;
end;

procedure TSoundRecorder.Rec(AFileName: String);
begin
  if FileExists(AFileName) then DeleteFile(PWideChar(AFileName));
  Stop;
  FMic.FileName:=AFileName;
  FMic.StartCapture;
end;

procedure TSoundRecorder.Stop;
begin
  if FMic.State = TCaptureDeviceState.Capturing then begin
    FMic.StopCapture;
  end;
  if FPlayer.State = TMediaState.Playing then begin
    FPlayer.Stop;
    FPlayer.Clear;
  end;
end;

{ TLogsOfTain2 }

constructor TLogsOfTain2a.Create(AUser,AConfig: String);
begin
  FUser:=AUser;
  FConfig:=AConfig;
  FConnection:=CreateLocalConnection(ExePath+'logs.db');
  FQuery:=TFDQuery.Create(Application.MainForm);
  FQuery.Connection:=FConnection;
  FQuery.Open('select * from logs where username='+AUser+' and config='+AConfig);
end;

destructor TLogsOfTain2a.Destroy;
begin
  FQuery.Close;
  FQuery.Free;
  FConnection.Close;
  FConnection.Free;
  inherited;
end;

procedure TLogsOfTain2a.WriteLog(ADate:TDate;ATime: TTime;ATarget: String; AMatch: Boolean; AReflectTime: Integer);
begin
  FQuery.AppendRecord([FUser,FConfig,ADate,ATime,ATarget,AMatch,AReflectTime]);
end;

{ TLogsOfTrain }

constructor TLogsOfTrain.Create(AUser, AConfig: String);
begin
  FUser:=AUser;
  FConfig:=AConfig;
  FConnection:=CreateLocalConnection(ExePath+'logs.db');
  FQuery:=TFDQuery.Create(Application.MainForm);
  FQuery.Connection:=FConnection;
  FQuery.Open('select * from logoftrain1');
end;

destructor TLogsOfTrain.Destroy;
begin
  FQuery.Close;
  FQuery.Free;
  FConnection.Close;
  FConnection.Free;
  inherited;
end;

procedure TLogsOfTrain.WriteLog(ALogOfTrain: TLogOfTrain);
begin
  FQuery.AppendRecord([FUser,FConfig,
                      ALogOfTrain.Date,ALogOfTrain.Time,
                      ALogOfTrain.Target,ALogOfTrain.Answer,ALogOfTrain.Match,
                      ALogOfTrain.ReflectTime]);
end;

{ TQuizCreator }

constructor TQuizCreator.Create;
begin

end;

destructor TQuizCreator.Destroy;
begin

  inherited;
end;

{ TWeightTraverse }

constructor TWeightTraverse.Create(ACount: Integer);
begin

end;

destructor TWeightTraverse.Destroy;
begin

  inherited;
end;

function TWeightTraverse.Index: Integer;
begin

end;

{ TChoiceQuizGenerator }

function TChoiceQuizGenerator.AnswerIsRight(AAnswer: String): Boolean;
begin
  Result:= (AAnswer = Quiz.Target);
end;

constructor TChoiceQuizGenerator.Create(AChoiceCount: Integer;ATargets:TStringList);
begin
  FXml:=TXmlWrap.Create;

  FTargets:=ATargets;
  FChoiceCount:=AChoiceCount;
  FTraverse:=TRandomTraverse.Create(FTargets.Count);
  Next;
end;

destructor TChoiceQuizGenerator.Destroy;
begin
  FXml.Free;
  FTraverse.Free;
  inherited;
end;

procedure TChoiceQuizGenerator.Next;
var Group1,Group2: TStringList;
    i:Integer;
    Index1: Integer;
    Fixed1: Boolean;
begin
  Group1:=TStringList.Create;
  Group2:=TStringList.Create;
  for i := 0 to FTargets.Count-1 do begin
    Group1.Add(FTargets.Strings[i]);
  end;
  Index1:=FTraverse.Index;
  Quiz.Target:=Group1.Strings[Index1];
  Group1.Delete(Index1);
  for i := 0 to FChoiceCount-2 do begin
    Index1:=Random(Group1.Count);
    Group2.Add(Group1.Strings[Index1]);
    Group1.Delete(Index1);
  end;

  Index1:=Random(FChoiceCount);
  Group2.Insert(Index1,Quiz.Target);

  if FTargets.Count<=4 then begin
    if FTargets.Count>0 then Quiz.Choices.Left:=FTargets.Strings[0];
    if FTargets.Count>1 then Quiz.Choices.Right:=FTargets.Strings[1];
    if FTargets.Count>2 then Quiz.Choices.Up:=FTargets.Strings[2];
    if FTargets.Count>3 then Quiz.Choices.Down:=FTargets.Strings[3];
  end else begin
    if FChoiceCount>0 then Quiz.Choices.Left:=Group2.Strings[0];
    if FChoiceCount>1 then Quiz.Choices.Right:=Group2.Strings[1];
    if FChoiceCount>2 then Quiz.Choices.Up:=Group2.Strings[2];
    if FChoiceCount>3 then Quiz.Choices.Down:=Group2.Strings[3];
  end;
end;

{
type: Init QuizGenerator:
value: <config>...</config>
<config>
  <ChoiceCount></ChoiceCount>
  <Targets>
    <Target>
      <Caption></Caption>
      <Sounds>
        <Sound></Sound>
        <Sound></Sound>
      </Sounds>
    </Target>
    <Target>
    </Target>
  </Targets>
</config>

type: Next Quiz
Value: null

type: Get Quiz
Value: <Quiz></Quiz>
}
procedure TChoiceQuizGenerator.Recv(const AType, AValue: String);
begin
  if AType='Init QuizGenerator' then begin
    FXml.Load(AValue);
  end;
  if AType='Next Quiz' then begin

  end;
  if AType='Get Quiz' then begin

  end;
end;

procedure TChoiceQuizGenerator.Send(const AType, AValue: String);
begin
  if Assigned(FOnEvent) then begin
    FOnEvent(AType,AValue);
  end;
end;

{ TXmlWrap }

procedure TXmlWrap.Assign(ADest, ASource: IXMLNode);
var Source1: IXMLNode;
    Child1: IXMLNode;
    i: Integer;
begin
  ADest.ChildNodes.Clear;
  for i := 0 to ASource.ChildNodes.Count-1 do begin
    Child1:=ASource.ChildNodes.Get(i).CloneNode(True);
    ADest.ChildNodes.Add(Child1);
  end;
end;

procedure TXmlWrap.AssignByPath(ANode: IXMLNode; ADestPath, ASourcePath: String);
var DestNode1,SourceNode1:IXMLNode;
begin
  DestNode1:=Self.SelectNode(ANode,ADestPath);
  SourceNode1:=SelectNode(ANode,ASourcePath);
  Assign(DestNode1,SourceNode1);
end;

procedure TXmlWrap.AssignTextByPath(ANode: IXMLNode;APath, AText: String);
var Node1: IXMLNode;
begin
  Node1:=SelectNode(ANode,APath);
  Node1.ChildNodes.Clear;
  Node1.Text:=AText;
end;

procedure TXmlWrap.AssignValueByPath(ANode: IXMLNode; APath: String;
  AValue: IXMLNode);
var Node1: IXMLNode;
begin
  Node1:=SelectNode(ANode,APath);
  Assign(Node1,AValue);
end;

procedure TXmlWrap.ConvertAllValueToDefault(ANode: IXMLNode);
var Node1: IXMLNode;
    i: Integer;
begin
  if ANode.ChildNodes.Count=1 then begin
    Node1:=ANode.ChildNodes.Get(0);
    if IsVar(ANode) then begin
      Assign(ANode,SelectNode(ANode,'var/default'));
    end else begin
      ConvertAllValueToDefault(Node1);
    end;
  end else if ANode.ChildNodes.Count>1 then begin
    for i := 0 to ANode.ChildNodes.Count-1 do begin
      ConvertAllValueToDefault(ANode.ChildNodes.Get(i));
    end;
  end;
end;

procedure TXmlWrap.ConvertVarToDefaultValue(ANode: IXMLNode);
var D1: String;
begin
  D1:=ANode.ChildNodes.FindNode('var').ChildNodes.FindNode('default').Text;
  ANode.ChildNodes.Clear;
  ANode.NodeValue:=D1;
end;

function TXmlWrap.Count(APath: String): Integer;
begin
  Result:=FSel.selectNodes(APath).length;
end;

constructor TXmlWrap.Create;
begin
  FXml:=TXMLDocument.Create(nil);
end;

destructor TXmlWrap.Destroy;
begin
  inherited;
end;

function TXmlWrap.Get(APath: String): String;
var Node1: IDOMNode;
begin
  Node1:=FSel.selectNode(APath);
  if Node1=nil then begin
    Result:='$none';
  end else begin
    Result:=Node1.firstChild.nodeValue;
  end;
end;

function TXmlWrap.Geti(APath: String; AIndex: Integer): String;
begin
  Result:=FSel.selectNodes(APath).item[AIndex].firstChild.nodeValue;
end;

function TXmlWrap.IsVar(ANode: IXMLNode): Boolean;
begin
  if ANode.ChildNodes.FindNode('var') = nil then begin
    Result:=False;
  end else begin
    Result:=True;
  end;
end;

procedure TXmlWrap.Load(AXML: String);
begin
  FXml.LoadFromXML(AXML);
  FXml.Active:=True;
  FSel:=FXml.DOMDocument as IDomNodeSelect;
end;

function TXmlWrap.LoadFile(AFileName: String): Boolean;
begin
  FXml.LoadFromFile(AFileName);
  FXml.Active:=True;
  FSel:=FXml.DOMDocument as IDomNodeSelect;
  Result:=True;
end;


function TXmlWrap.NameOfVar(AVar: IXMLNode): String;
begin
  Result:=AVar.ChildNodes.FindNode('name').Text;
end;

function TXmlWrap.PathOfNode(ARNode, ANode: IXMLNode): String;
var Node1: IXMLNode;
    Path1: String;
begin
  if ANode=ARNode then begin
    Result:=ANode.NodeName;
  end else begin
    Node1:=ANode.ParentNode;
    Path1:=PathOfNode(ARNode,Node1);
    Result:=Path1+'/'+ANode.NodeName;
  end;
end;

function TXMLWrap.PathOfVar(ANode, AVar: IXMLNode): String;
var R1: String;
    Child1: IXMLNode;
    i: Integer;
begin
  //ShowMessage(ANode.XML);
  //ShowMessage(AVar.XML);
  if ANode.ChildNodes.Count=0 then begin
    Result:='';
  end else begin
    Result:='';
    for i := 0 to ANode.ChildNodes.Count-1 do begin
      Child1:=ANode.ChildNodes.Get(i);
      if IsVar(Child1) then begin
        if NameOfVar(Child1.ChildNodes.Get(0)) =AVar.NodeName then begin
          Result:=Child1.NodeName;
        end;
      end else begin
        R1:=PathOfVar(Child1,AVar);
        if R1<>'' then begin
          Result:=Child1.NodeName+'/'+R1;
        end;
      end;
    end;
  end;
end;

procedure TXmlWrap.Replace(ADest, ASource: IXMLNode);
begin
  ADest.ChildNodes.ReplaceNode(ADest,ASource);
end;

function TXmlWrap.RootNode: IXMLNode;
begin
  Result:=FXml.ChildNodes.Get(0);
end;

function TXmlWrap.SelectNode(ANode: IXMLNode;APath: String): IXMLNode;
var Node1: IXMLNode;
    NodeName1: String;
    Path1: String;
    i: Integer;
begin
  i:=Pos('/',APath);
  if i=0 then begin
    NodeName1:=APath;
    Result:=ANode.ChildNodes.FindNode(NodeName1);
  end else begin
    NodeName1:=Copy(APath,0,i-1);
    Path1:=Copy(APath,i+1,Length(APath));
    Node1:=ANode.ChildNodes.FindNode(NodeName1);
    Result:=SelectNode(Node1,Path1);
  end;
end;


{ TMultiEventClass }
//create the event handlers storage
constructor TMultiEventClass.Create;
begin
  inherited Create;
  fMultiEventHandlers := TList.Create;
end;
//clean up
destructor TMultiEventClass.Destroy;
var
  cnt: Integer;
begin
  for cnt := 0 to -1 + fMultiEventHandlers.Count do
    Dispose(fMultiEventHandlers[cnt]);
  fMultiEventHandlers.Free;
  inherited;
end;
//add an event handler to the list
procedure TMultiEventClass.Add(AEvent: TMultiEvent);
var
  h: PMethod;
begin
  h := New(PMethod);
  h^.Code := TMethod(AEvent).Code;
  h^.Data := TMethod(AEvent).Data;
  fMultiEventHandlers.Add(h);
end;
//remove an event handler from the list (if there)
procedure TMultiEventClass.Remove(AEvent: TMultiEvent);
var
  cnt: Integer;
begin
  for cnt := 0 to -1 + fMultiEventHandlers.Count  do
  begin
    if (TMethod(AEvent).Code =
        TMethod(fMultiEventHandlers[cnt]^).Code) and
       (TMethod(AEvent).Data =
        TMethod(fMultiEventHandlers[cnt]^).Data) then
    begin
      Dispose(fMultiEventHandlers[cnt]);
      fMultiEventHandlers.Delete(cnt);
      Break;
    end;
  end;
end;
procedure TMultiEventClass.Fire(AType,AMsg: String);
var
  cnt: Integer;
  msg : string;
begin
  for cnt := 0 to -1 + fMultiEventHandlers.Count do
  begin
    TMultiEvent(fMultiEventHandlers[cnt]^)(AType,AMsg);
  end;
end;

{ TWavLists }

constructor TWavLists.Create(ADir: String);
var List1: TStringDynArray;
    FileList1: TStringList;
    i: Integer;
    Caption1: String;
begin
  FPlayer:=TMediaPlayer.Create(nil);
  FLists:=TStringList.Create;
  List1:=TDirectory.GetDirectories(ADir);
  for i:=0 to Length(List1)-1 do begin
    Caption1:=ExtractRelativePath(ExePath+'resource\',List1[i]);
    FLists.AddObject(Caption1,CreateFileList(List1[i]));
  end;
end;

function TWavLists.CreateFileList(ADir: String): TStringList;
var List1: TStringDynArray;
    i: Integer;
begin
  List1:=TDirectory.GetFiles(ADir);
  Result:=TStringList.Create;
  for i := 0 to Length(List1)-1 do begin
    Result.Add(List1[i]);
  end;
end;

destructor TWavLists.Destroy;
var i:Integer;
begin
  for i := 0 to FLists.Count-1 do begin
    FLists.Objects[i].Free;
  end;
  FLists.Free;
  FPlayer.Free;
  inherited;
end;

function TWavLists.Play(AStr: String): String;
var FileName1: String;
    List1: TStringList;
    i,Index1: Integer;
begin
  Index1:=FLists.IndexOf(AStr);
  List1:=TStringList(FLists.Objects[Index1]);
  i:=Random(List1.Count);
  FileName1:=List1.Strings[i];
  FPlayer.Stop;
  FPlayer.FileName:=FileName1;
  FPlayer.Play;
  Result:=FileName1;
end;

{ TPlayList }

function TPlayList.CountFiles(ADir: String): Integer;
begin
  Result:=Length(TDirectory.GetFiles(ADir));
end;

constructor TPlayList.Create;
begin
  FState:=0;
  FCount:=0;
  FTimer:=TTimer.Create(nil);
  FTimer.Interval:=10;
  FTimer.Enabled:=False;
  FTimer.OnTimer:=TimerEvent;
  FPlayer:=TMediaPlayer.Create(nil);
end;

destructor TPlayList.Destroy;
begin
  FState:=0;
  FCount:=0;
  FTimer.Free;
  FPlayer.Stop;
  FPlayer.Free;
  inherited;
end;

procedure TPlayList.Play(ADir: String);
var SearchRec1: TSearchRec;
begin
  FDir:=ADir;
  FCount:=CountFiles(ADir);
  FState:=1;
  FPlayer.FileName:=FDir+'\'+IntToStr(FState)+'.wav';
  FPlayer.Play;
  FTimer.Enabled:=True;
end;

procedure TPlayList.TimerEvent(ASender: TObject);
begin
  if FPlayer.CurrentTime<FPlayer.Duration then begin
    Exit;
  end;
  if FPlayer.CurrentTime=FPlayer.Duration then begin
    FState:=FState+1;
    if FState<=FCount then begin
      FPlayer.FileName:=FDir+'\'+IntToStr(FState)+'.wav';
      FPlayer.Play;
    end else begin
      FState:=0;
      FCount:=0;
      FTimer.Enabled:=False;
    end;
  end;
end;

function GetMD5Hash(Str:String):String;
var md5 : TIdHashMessageDigest5;
begin
  md5 := TIdHashMessageDigest5.Create;
  Result:=md5.HashStringAsHex(Str);
  md5.Free;
end;

{ TServerApi }

constructor TServerApi.Create;
begin
  FIdHttp:=TIdHttp.Create;
end;

destructor TServerApi.Destroy;
begin
  FIdHttp.Free;
  inherited;
end;


function TServerApi.Post(AUrl: String; AParams, AValues: array of String): String;
var DataToSend: TStringList;
    i: Integer;
    TokenStr,TokenHash: String;
begin
  DataToSend:=TStringList.Create;
  TokenStr:='';
  for i := 0 to Length(AParams)-1 do begin
    DataToSend.Add(AParams[i]+'='+AValues[i]);
    TokenStr:= TokenStr+AParams[i]+AValues[i];
  end;
  TokenStr:=TokenStr+'`@2015~@*TTT~na*()';
  TokenHash:=LowerCase(GetMD5Hash(TokenStr));
  DataToSend.Add('token='+TokenHash);
  Result:=FIdHttp.Post(AUrl, DataToSend);
  DataToSend.Free;
end;

function TServerApi.ServerTime: String;
var ApiUrl: String;
    Params,Values: array[0..0] of String;
begin
  Params[0]:='time';
  Values[0]:=IntToStr(GetTickCount);
  ApiUrl:='http://123.57.240.210/index.php?g=api&m=sys&a=sysdate';
  Result:=Post(ApiUrl,Params,Values);
end;

function TServerApi.StartTraining(AUid: String): String;
var ApiUrl: String;
    Params,Values: array[0..1] of String;
begin
  Params[0]:='time';
  Values[0]:=IntToStr(GetTickCount);
  Params[1]:='uid';
  Values[1]:=AUid;
  ApiUrl:='http://123.57.240.210/index.php?g=api&m=table&a=start';
  Result:=Post(ApiUrl,Params,Values);
end;

function TServerApi.TrainLog(AUid, ATrainId, AXmlVal: String): String;
var ApiUrl: String;
    Params,Values: array[0..3] of String;
begin
  Params[0]:='time';
  Values[0]:=IntToStr(GetTickCount);
  Params[1]:='trainid';
  Values[1]:=ATrainId;
  Params[2]:='uid';
  Values[2]:=AUid;
  Params[3]:='xmlval';
  Values[3]:=AXmlVal;
  ApiUrl:='http://123.57.240.210/index.php?g=api&m=train&a=log';
  Result:=Post(ApiUrl,Params,Values);
end;

function TServerApi.TrainVersion(ATrainId:String): String;
var ApiUrl: String;
    Params,Values: array[0..1] of String;
begin
  Params[0]:='time';
  Values[0]:=IntToStr(GetTickCount);
  Params[1]:='trainid';
  Values[1]:=ATrainId;
  ApiUrl:='http://123.57.240.210/index.php?g=api&m=train&a=ver';
  Result:=Post(ApiUrl,Params,Values);
end;

function TServerApi.GetTable: String;
var ApiUrl: String;
    Params,Values: array[0..0] of String;
begin
  Params[0]:='time';
  Values[0]:=IntToStr(GetTickCount);
  ApiUrl:='http://123.57.240.210/index.php?g=api&m=table&a=show';
  Result:=Post(ApiUrl,Params,Values);
end;

function TServerApi.MainVersion: String;
var ApiUrl: String;
    Params,Values: array[0..0] of String;
begin
  Params[0]:='time';
  Values[0]:=IntToStr(GetTickCount);
  ApiUrl:='http://123.57.240.210/index.php?g=api&m=sys&a=ver';
  Result:=Post(ApiUrl,Params,Values);
end;

function TServerApi.UpdateTrain(AUid, ATableId, ATrainId,
  AProgress: String): String;
var ApiUrl: String;
    Params,Values: array[0..4] of String;
begin
  Params[0]:='progress';
  Params[1]:='tableid';
  Params[2]:='time';
  Params[3]:='trainid';
  Params[4]:='uid';
  Values[0]:=AProgress;
  Values[1]:=ATableId;
  Values[2]:=IntToStr(GetTickCount);
  Values[3]:=ATrainId;
  Values[4]:=AUid;
  ApiUrl:='http://123.57.240.210/index.php?g=api&m=table&a=updatetrain';
  Result:=Post(ApiUrl,Params,Values);
end;

function TServerApi.UserForgetPassword(APhone, APassword, APasswordConfirm,
  ACaptcha: String): String;
var ApiUrl: String;
    Params,Values: array[0..4] of String;
begin
  Params[0]:='captcha';
  Params[1]:='password';
  Params[2]:='passwordconfirm';
  Params[3]:='phone';
  Params[4]:='time';
  Values[0]:=ACaptcha;
  Values[1]:=APassword;
  Values[2]:=APasswordConfirm;
  Values[3]:=APhone;
  Values[4]:=IntToStr(GetTickCount);
  ApiUrl:='http://123.57.240.210/index.php?g=api&m=users&a=changepass';
  Result:=Post(ApiUrl,Params,Values);
end;

function TServerApi.UserGetCaptcha(APhone: String): String;
var DataToSend: TStringList;
    ApiUrl: String;
    Params,Values: array[0..1] of String;
begin
  Params[0]:='phone';
  Values[0]:=APhone;
  Params[1]:='time';
  Values[1]:=IntToStr(GetTickCount);
  ApiUrl:='http://123.57.240.210/index.php?g=api&m=users&a=captcha';
  Result:=Post(ApiUrl,Params,Values);
end;

function TServerApi.UserGetInfomation(AUid: String): String;
var ApiUrl: String;
    Params,Values: array[0..1] of String;
begin
  Params[0]:='time';
  Values[0]:=IntToStr(GetTickCount);
  Params[1]:='uid';
  Values[1]:=AUid;
  ApiUrl:='http://123.57.240.210/index.php?g=api&m=users&a=getuserinfo';
  Result:=Post(ApiUrl,Params,Values);

end;

function TServerApi.UserGetProgress(AUid: String): String;
var ApiUrl: String;
    Params,Values: array[0..1] of String;
begin
  Params[0]:='time';
  Params[1]:='uid';
  Values[0]:=IntToStr(GetTickCount);
  Values[1]:=AUid;
  ApiUrl:='http://123.57.240.210/index.php?g=api&m=table&a=profile';
  Result:=Post(ApiUrl,Params,Values);
end;

function TServerApi.UserLogin(APhone, APassword: String): String;
var ApiUrl: String;
    Params,Values: array[0..2] of String;
begin
  Params[0]:='password';
  Params[1]:='time';
  Params[2]:='username';
  Values[0]:=APassword;
  Values[1]:=IntToStr(GetTickCount);
  Values[2]:=APhone;
  ApiUrl:='http://123.57.240.210/index.php?g=api&m=users&a=login';
  Result:=Post(ApiUrl,Params,Values);
end;

function TServerApi.UserRegist(APhone, APassword,APasswordConfirm,ACaptcha: String): String;
var ApiUrl: String;
    Params,Values: array[0..4] of String;
begin
  Params[0]:='captcha';
  Params[1]:='password';
  Params[2]:='passwordconfirm';
  Params[3]:='phone';
  Params[4]:='time';
  Values[0]:=ACaptcha;
  Values[1]:=APassword;
  Values[2]:=APasswordConfirm;
  Values[3]:=APhone;
  Values[4]:=IntToStr(GetTickCount);
  ApiUrl:= 'http://123.57.240.210/index.php?g=api&m=users&a=reg';
  Result:=Post(ApiUrl,Params,Values);
end;

end.

